
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>   //uintptr_t
#include <stdarg.h>   //va_start....
#include <unistd.h>   //STDERR_FILENO等
#include <sys/time.h> //gettimeofday
#include <time.h>     //localtime_r
#include <fcntl.h>    //open
#include <errno.h>    //errno
// #include <sys/socket.h>
#include <sys/ioctl.h> //ioctl
#include <arpa/inet.h>

#include "ngx_c_conf.h"
#include "ngx_macro.h"
#include "ngx_global.h"
#include "ngx_func.h"
#include "ngx_c_socket.h"
#include "ngx_c_memory.h"
#include "ngx_c_socket_conn.hpp"
#include "ngx_c_socket_request.hpp"
// 构造函数
CSocekt::CSocekt()
{
    // 配置相关
    m_worker_connections = 1; // epoll连接最大项数
    m_ListenPortCount = 1;    // 监听一个端口

    // epoll相关
    m_epollhandle = -1;            // epoll返回的句柄
    m_pconnections = nullptr;      // 连接池【连接数组】先给空
    m_pfree_connections = nullptr; // 连接池中空闲的连接链
    // m_pread_events = NULL;       //读事件数组给空
    // m_pwrite_events = NULL;      //写事件数组给空

    // 一些和网络通讯有关的常用变量值，供后续频繁使用时提高效率
    m_iLenPkgHeader = sizeof(COMM_PKG_HEADER);  // 包头的sizeof值【占用的字节数】
    m_iLenMsgHeader = sizeof(STRUC_MSG_HEADER); // 消息头的sizeof值【占用的字节数】

    return;
}

// 释放函数
CSocekt::~CSocekt()
{
    // 释放必须的内存
    //(1)监听端口相关内存的释放--------
    std::vector<lpngx_listening_t>::iterator pos;
    for (pos = m_ListenSocketList.begin(); pos != m_ListenSocketList.end(); ++pos) // vector
    {
        delete (*pos);
    } // end for
    m_ListenSocketList.clear();

    //(2)连接池相关的内容释放---------
    // if(m_pwrite_events != NULL)//释放写事件数组
    //    delete [] m_pwrite_events;

    // if(m_pread_events != NULL)//释放读事件数组
    //     delete [] m_pread_events;

    if (m_pconnections != NULL) // 释放连接池
        delete[] m_pconnections;

    //(3)接收消息队列中内容释放
    clearMsgRecvQueue();

    return;
}

//各种清理函数-------------------------
//清理接收消息队列，注意这个函数的写法。
void CSocekt::clearMsgRecvQueue()
{
	char * sTmpMempoint;
	CMemory *p_memory = CMemory::GetInstance();

	//临界与否，日后再考虑，当前先不考虑。。。。。。如果将来有线程池再考虑临界问题
	while(!m_MsgRecvQueue.empty())
	{
		sTmpMempoint = m_MsgRecvQueue.front();		
		m_MsgRecvQueue.pop_front(); 
		p_memory->FreeMemory(sTmpMempoint);
	}	
}

// 初始化函数【fork()子进程之前干这个事】
// 成功返回true，失败返回false
bool CSocekt::Initialize()
{
    ReadConf();                               // 读配置项
    bool reco = ngx_open_listening_sockets(); // 打开监听端口
    return reco;
}

// 专门用于读各种配置项
void CSocekt::ReadConf()
{
    CConfig *p_config = CConfig::GetInstance();
    m_worker_connections = p_config->GetIntDefault("worker_connections", m_worker_connections); // epoll连接的最大项数
    m_ListenPortCount = p_config->GetIntDefault("ListenPortCount", m_ListenPortCount);          // 取得要监听的端口数量
    return;
}

// 监听端口【支持多个端口】创建一批监听套接字
// 在创建worker进程之前就要执行这个函数；
bool CSocekt::ngx_open_listening_sockets()
{
    int isock;                    // socket
    struct sockaddr_in serv_addr; // 服务器的地址结构体
    int iport;                    // 端口
    char strinfo[100];            // 临时字符串

    // 初始化
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET; // ipv4
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 中途用到一些配置信息
    CConfig *p_config = CConfig::GetInstance();
    for (int i = 0; i < m_ListenPortCount; i++) // 要监听这么多个端口
    {
        isock = socket(AF_INET /*ipv4*/, SOCK_STREAM /*TCP*/, 0); // 创建监听套接字，出错返回-1
        if (isock == -1)
        {
            ngx_log_stderr(errno, "CSocekt::Initialize()中socket()失败,i=%d.", i);
            // 其实这里直接退出，那如果以往有成功创建的socket呢？就没得到释放吧，当然走到这里表示程序不正常，应该整个退出，也没必要释放了
            return false;
        }

        int reuseaddr = 1;                                                                                  // 1:打开对应的设置项
        if (setsockopt(isock, SOL_SOCKET, SO_REUSEADDR, (const void *)&reuseaddr, sizeof(reuseaddr)) == -1) // 设置地址复用
        {
            ngx_log_stderr(errno, "CSocekt::Initialize()中setsockopt(SO_REUSEADDR)失败,i=%d.", i);
            close(isock); // 无需理会是否正常执行了
            return false;
        }
        // 设置该socket为非阻塞
        if (setnonblocking(isock) == false)
        {
            ngx_log_stderr(errno, "CSocekt::Initialize()中setnonblocking()失败,i=%d.", i);
            close(isock);
            return false;
        }

        // 设置端口号
        strinfo[0] = 0;
        sprintf(strinfo, "ListenPort%d", i);
        iport = p_config->GetIntDefault(strinfo, 10000);
        serv_addr.sin_port = htons((in_port_t)iport); // in_port_t其实就是uint16_t

        // 绑定端口号
        if (bind(isock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
        {
            ngx_log_stderr(errno, "CSocekt::Initialize()中bind()失败,i=%d.", i);
            close(isock);
            return false;
        }

        // 开始监听
        if (listen(isock, NGX_LISTEN_BACKLOG) == -1)
        {
            ngx_log_stderr(errno, "CSocekt::Initialize()中listen()失败,i=%d.", i);
            close(isock);
            return false;
        }

        // 可以，放到列表里来
        lpngx_listening_t p_listensocketitem = new ngx_listening_t; // 千万不要写错，注意前边类型是指针，后边类型是一个结构体
        memset(p_listensocketitem, 0, sizeof(ngx_listening_t));
        p_listensocketitem->port = iport;
        p_listensocketitem->fd = isock;
        ngx_log_error_core(NGX_LOG_INFO, 0, "监听%d端口成功!", iport); // 显示一些信息到日志中
        m_ListenSocketList.push_back(p_listensocketitem);              // 加入到队列中
    } // end for()
    if (m_ListenSocketList.size() <= 0)
        return false;
    return true;
}

bool CSocekt::setnonblocking(int sockfd)
{
    int nb = 1;                            // 0：清除，1：设置
    if (ioctl(sockfd, FIONBIO, &nb) == -1) // FIONBIO：设置/清除非阻塞I/O标记：0：清除，1：设置
    {
        return false;
    }
    return true;
}

// 关闭监听套接字
void CSocekt::ngx_close_listening_sockets()
{
    for (int i = 0; i < m_ListenPortCount; i++) // 要关闭这么多个监听端口
    {
        // ngx_log_stderr(0,"端口是%d,socketid是%d.",m_ListenSocketList[i]->port,m_ListenSocketList[i]->fd);
        close(m_ListenSocketList[i]->fd);
        ngx_log_error_core(NGX_LOG_INFO, 0, "关闭监听端口%d!", m_ListenSocketList[i]->port); // 显示一些信息到日志中
    } // end for(int i = 0; i < m_ListenPortCount; i++)
    return;
}

//--------------------------------------------------------------------
//(1)epoll功能初始化，子进程中进行 ，本函数被ngx_worker_process_init()所调用
int CSocekt::ngx_epoll_init()
{
    // 创建epoll模型
    m_epollhandle = epoll_create(m_worker_connections /*大于0即可*/);
    if (m_epollhandle == -1)
    {
        ngx_log_stderr(errno, "CSocekt::ngx_epoll_init()中epoll_create()失败.");
        exit(2); // 直接退
    }

    //(2)创建连接池后续用于处理所有客户端的连接
    m_connection_n = m_worker_connections; // 记录当前连接池中连接总数
    // 连接池【数组，每个元素是一个对象】
    m_pconnections = new ngx_connection_t[m_connection_n]; // new不可以失败，不用判断结果，如果失败直接报异常更好一些

    int i = m_connection_n; // 连接池中连接数
    lpngx_connection_t next = nullptr;
    lpngx_connection_t c = m_pconnections; // 连接池数组首地址
    // 尾插
    do
    {
        i--;
        // 初始化
        c[i].data = next;
        c[i].fd = -1;
        c[i].instance = 1;      // 失效标志 【1】表示失效
        c[i].iCurrsequence = 0; // 当前序号统一从0开始
        next = &c[i];           // next指针前移
    } while (i);
    m_pfree_connections = next;           // 指向空闲连接->c[0]
    m_free_connection_n = m_connection_n; // 空闲连接链表长度，因为现在整个链表都是空的，这两个长度相等；

    // 遍历监听套接字
    for (auto pos = m_ListenSocketList.begin(); pos != m_ListenSocketList.end(); ++pos)
    {
        c = ngx_get_connection((*pos)->fd); // 从连接池中获取一个空闲连接对象
        if (c == nullptr)
        {
            // 这是致命问题，刚开始怎么可能连接池就为空呢？
            ngx_log_stderr(errno, "CSocekt::ngx_epoll_init()中ngx_get_connection()失败.");
            exit(2); // 这是致命问题了，直接退
        }
        c->listening = (*pos);
        (*pos)->connection = c;
        // rev->accept = 1;
        c->rhandler = &CSocekt::ngx_event_accept;

        if (ngx_epoll_add_event((*pos)->fd,    // socekt句柄
                                1, 0,          // 读，写【只关心读事件，所以参数2：readevent=1,而参数3：writeevent=0】
                                0,             // 其他补充标记
                                EPOLL_CTL_ADD, // 事件类型【增加，还有删除/修改】
                                c              // 连接池中的连接
                                ) == -1)
        {
            exit(2); // 有问题，直接退出，日志 已经写过了
        }
    } // end for()
    return 1;
}

/*
//(2)监听端口开始工作，监听端口要开始工作，必须为其增加读事件，因为监听端口只关心读事件
void CSocekt::ngx_epoll_listenportstart()
{
    std::vector<lpngx_listening_t>::iterator pos;
    for(pos = m_ListenSocketList.begin(); pos != m_ListenSocketList.end(); ++pos) //vector
    {
        //本函数如果失败，直接退出
        ngx_epoll_add_event((*pos)->fd,1,0); //只关心读事件
    } //end for
    return;
}
*/

// epoll增加事件，可能被ngx_epoll_init()等函数调用
// fd:句柄，一个socket
// readevent：表示是否是个读事件，0是，1不是
// writeevent：表示是否是个写事件，0是，1不是
// otherflag：其他需要额外补充的标记，弄到这里
// eventtype：事件类型  ，一般就是用系统的枚举值，增加，删除，修改等;
// c：对应的连接池中的连接的指针
// 返回值：成功返回1，失败返回-1；
int CSocekt::ngx_epoll_add_event(int fd,
                                 int readevent, int writeevent,
                                 uint32_t otherflag,
                                 uint32_t eventtype,
                                 lpngx_connection_t c)
{
    struct epoll_event ev;
    // int op;
    memset(&ev, 0, sizeof(ev));

    if (readevent == 1)
    {
        ev.events = EPOLLIN | EPOLLRDHUP; // EPOLLRDHUP 客户端关闭连接，断连
    }
    else
    {
        // TODO
    }

    if (otherflag != 0)
    {
        ev.events |= otherflag;
    }

    ev.data.ptr = (void *)((uintptr_t)c | c->instance); // 连接指针和instance放进去（指针是4或8的倍数最后一位可以用）
    if (epoll_ctl(m_epollhandle, eventtype, fd, &ev) == -1)
    {
        ngx_log_stderr(errno, "CSocekt::ngx_epoll_add_event()中epoll_ctl(%d,%d,%d,%u,%u)失败.", fd, readevent, writeevent, otherflag, eventtype);
        // exit(2); //这是致命问题了，直接退，资源由系统释放吧，这里不刻意释放了，比较麻烦，后来发现不能直接退；
        return -1;
    }
    return 1;
}

// epoll_wait
// 参数unsigned int timer：epoll_wait()阻塞的时长，单位是毫秒；
// 返回值，1：正常返回  ,0：有问题返回，一般不管是正常还是问题返回，都应该保持进程继续运行
// 本函数被ngx_process_events_and_timers()调用，而ngx_process_events_and_timers()是在子进程的死循环中被反复调用
int CSocekt::ngx_epoll_process_events(int timer)
{
    int events = epoll_wait(m_epollhandle, m_events, NGX_MAX_EVENTS, timer);

    if (events == -1)
    {

        if (errno == EINTR)
        {
            // 信号所致，直接返回，一般认为这不是毛病，但还是打印下日志记录一下，因为一般也不会人为给worker进程发送消息
            ngx_log_error_core(NGX_LOG_INFO, errno, "CSocekt::ngx_epoll_process_events()中epoll_wait()失败!");
            return 1; // 正常返回
        }
        else
        {
            // 这被认为应该是有问题，记录日志
            ngx_log_error_core(NGX_LOG_ALERT, errno, "CSocekt::ngx_epoll_process_events()中epoll_wait()失败!");
            return 0; // 非正常返回
        }
    }

    if (events == 0) // 超时，但没事件来
    {
        if (timer != -1)
        {
            // 要求epoll_wait阻塞一定的时间而不是一直阻塞，这属于阻塞到时间了，则正常返回
            return 1;
        }
        // 无限等待【所以不存在超时】，但却没返回任何事件，这应该不正常有问题
        ngx_log_error_core(NGX_LOG_ALERT, 0, "CSocekt::ngx_epoll_process_events()中epoll_wait()没超时却没返回任何事件!");
        return 0; // 非正常返回
    }

    // 会惊群，一个telnet上来，4个worker进程都会被惊动，都执行下边这个
    //ngx_log_stderr(errno, "惊群测试1:%d", events);

    // 走到这里，就是属于有事件收到了
    lpngx_connection_t c;
    uintptr_t instance;
    uint32_t revents;
    for (int i = 0; i < events; ++i)
    {
        c = (lpngx_connection_t)m_events[i].data.ptr;
        instance = (uintptr_t)c & 1;
        c = (lpngx_connection_t)((uintptr_t)c & (uintptr_t)~1); // 把最后一位干掉

        if (c->fd == -1)
        {
            ngx_log_error_core(NGX_LOG_DEBUG, 0, "CSocekt::ngx_epoll_process_events()中遇到了fd=-1的过期事件:%p.", c);
            continue; // 这种事件就不处理即可
        }

        // a)处理第一个事件时，因为业务需要，我们把这个连接【假设套接字为50】关闭，同时设置c->fd = -1;并且调用ngx_free_connection将该连接归还给连接池；
        // b)处理第二个事件，恰好第二个事件是建立新连接事件，调用ngx_get_connection从连接池中取出的连接非常可能就是刚刚释放的第一个事件对应的连接池中的连接；
        // c)又因为a中套接字50被释放了，所以会被操作系统拿来复用，复用给了b)【一般这么快就被复用也是醉了】；
        // d)当处理第三个事件时，第三个事件其实是已经过期的，应该不处理，那怎么判断这第三个事件是过期的呢？ 【假设现在处理的是第三个事件，此时这个 连接池中的该连接 实际上已经被用作第二个事件对应的socket上了】；
        // 依靠instance标志位能够解决这个问题，当调用ngx_get_connection从连接池中获取一个新连接时，我们把instance标志位置反，所以这个条件如果不成立，说明这个连接已经被挪作他用了；
        if (c->instance != instance)
        {
            ngx_log_error_core(NGX_LOG_DEBUG, 0, "CSocekt::ngx_epoll_process_events()中遇到了instance值改变的过期事件:%p.", c);
            continue; // 这种事件就不处理即可
        }

        // 能走到这里，我们认为这些事件都没过期，就正常开始处理
        revents = m_events[i].events;        // 取出事件类型
        if (revents & (EPOLLERR | EPOLLHUP)) // 例如对方close掉套接字，这里会感应到【换句话说：如果发生了错误或者客户端断连】
        {
            // 这加上读写标记，方便后续代码处理
            revents |= EPOLLIN | EPOLLOUT; // EPOLLIN：表示对应的链接上有数据可以读出（TCP链接的远端主动关闭连接，也相当于可读事件，因为本服务器小处理发送来的FIN包）
                                           // EPOLLOUT：表示对应的连接上可以写入数据发送【写准备好】
            // ngx_log_stderr(errno,"2222222222222222222222222.");
        }
        if (revents & EPOLLIN) // 如果是读事件
        {
            // c->r_ready = 1;               //标记可以读；【从连接池拿出一个连接时这个连接的所有成员都是0】
            (this->*(c->rhandler))(c);
        }

        if (revents & EPOLLOUT) // 如果是写事件
        {
            //....待扩展

            ngx_log_stderr(errno, "111111111111111111111111111111.");
        }
    } // end for(int i = 0; i < events; ++i)
    return 1;
}